/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package phd.dupenois.changeidentifiers.optimisation.graphs;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import phd.dupenois.changeidentifiers.ChangeIdentifierSet;
import phd.dupenois.changeidentifiers.optimisation.singlepattern.frames.GraphPanel;

/**
 *
 * @author Max
 */
public class OptimisationGraph extends GraphPanel{

    public static class SetDetails {

        private ChangeIdentifierSet set;
        private double timeTaken;
        private double error;
        private Color colour;

        public SetDetails(ChangeIdentifierSet set, double timeTaken, double error) {
            this.set = set;
            this.timeTaken = timeTaken;
            this.error = error;
            this.colour =
                    OptimisationGraph.colourMappings.get(set.getChangeIdentifierHolders()[0].getClassname());
            if (this.colour == null) {
                this.colour = Color.GRAY;
            }
        }

        public ChangeIdentifierSet getSet() {
            return set;
        }

        public double getTimeTaken() {
            return timeTaken;
        }

        public double getError() {
            return error;
        }

        public Color getColour() {
            return colour;
        }
    }
    private static HashMap<String, Color> colourMappings = new HashMap<String, Color>();

    static {
        colourMappings.put("phd.dupenois.changeidentifiers.areaboundingbox.AreaOfBoundingBox",
                Color.RED);
        colourMappings.put("phd.dupenois.changeidentifiers.areaboundingbox.AreaSymmetricDifferenceBoundingBox",
                Color.BLACK);
        colourMappings.put("phd.dupenois.changeidentifiers.centroidmovement.ChangeInCentroidScaled",
                Color.BLUE);
        colourMappings.put("phd.dupenois.changeidentifiers.timestepcount.TimeStepCount",
                Color.MAGENTA);
    };
    
    private HashMap<Point, SetDetails> front;
    private boolean frontLocked;
    private Point selectedPoint;
    private int pointDiameter = 8;
    private Point[] points;
    private HashMap<Point, SetDetails> frontClone;
//    private Point mutation;

    public OptimisationGraph() {
        this.setBackground(Color.WHITE);
        setXRange(0, 50);
        setYRange(0, 100);
        setScaleY(2);
        setScaleX(10);
        setxAxisTitle("Total Error (Symmetric Area)");
        setyAxisTitle("Total Time Taken (ms)");
        front = new HashMap<Point, SetDetails>();
        points = new Point[0];
        this.addMouseListener(this);
    }
    @Override
    public void addBaseLine(boolean x, double value){
        super.addBaseLine(x, value);
        if(!x) setYRange(0, (int)Math.round(value)+10);
    }
    public void addPoint(double timeTaken, double error, ChangeIdentifierSet set) {
        Point actPoint = new Point((int) Math.ceil(error), (int) Math.ceil(timeTaken));
//        Point graphPoint = getScreenPosition(actPoint);
        SetDetails setDetails = new SetDetails(set, timeTaken, error);
//        double maxX = getMaxX();
//        double maxY = getMaxY();
//        if(actPoint.getX()>maxX) setXRange(0, (int)Math.ceil(actPoint.getX()));
//        if(actPoint.getY()>maxY) setYRange(0, (int)Math.ceil(actPoint.getY()));

        this.frontLocked = true;
        this.front.put(actPoint, setDetails);
        this.frontLocked = false;
        this.repaint();
    }

    public void removePoint(ChangeIdentifierSet set) {
        this.frontLocked = true;
        Point removeKey = null;
        Iterator<Point> keyIter = this.front.keySet().iterator();
        Point key;
        SetDetails details;
        double maxX = 0;
        double maxY = 0;
        while (keyIter.hasNext()) {
            key = keyIter.next();
            details = this.front.get(key);
            if (details.getSet().equals(set)) {
                removeKey = key;
            } else {
                if (details.getError() > maxX) {
                    maxX = details.getError();
                }
                if (details.getTimeTaken() > maxY) {
                    maxY = details.getTimeTaken();
                }
            }
        }
        if (removeKey != null) {
            this.front.remove(removeKey);
        }
//        setXRange(0, (int)Math.ceil(maxX));
//        setYRange(0, (int)Math.ceil(maxY));
        this.frontLocked = false;
//        rescale();
        this.repaint();
        getFrame().repaint();

    }

    public void setFinishedSet(SetDetails[] sets) {
        this.frontLocked = true;
        HashMap<Point, SetDetails> finishedFront = new HashMap<Point, SetDetails>();
        Point actPoint;
        Point graphPoint;
        double maxX = 0;
        double maxY = 0;
        for (SetDetails set : sets) {
            if (set.getError() > maxX) {
                maxX = set.getError();
            }
            if (set.getTimeTaken() > maxY) {
                maxY = set.getTimeTaken();
            }
        }

        setXRange(0, (int) Math.ceil(maxX));
        setYRange(0, (int) Math.ceil(maxY));

        System.out.println("Max X : " + maxX);
        System.out.println("Max Y : " + maxY);

        if (maxY <= 10) {
            setScaleY(10);
        } else if (maxY <= 100) {
            setScaleY(2);
        } else if (maxY <= 1000) {
            setScaleY(1);
        } else if (maxY <= 10000) {
            setScaleY(.1);
        } else {
            setScaleY(.01);
        }


        if (maxX <= 10) {
            setScaleX(10);
        } else if (maxX <= 100) {
            setScaleX(10);
        } else if (maxX <= 1000) {
            setScaleX(.1);
        } else if (maxX <= 10000) {
            setScaleX(.01);
        } else {
            setScaleX(.001);
        }

        for (SetDetails set : sets) {
            actPoint = new Point((int) Math.ceil(set.getError()), (int) Math.ceil(set.getTimeTaken()));
//            graphPoint = getScreenPosition(actPoint);
            finishedFront.put(actPoint, set);
        }


        this.front = finishedFront;
        this.frontLocked = false;
        this.repaint();
        getFrame().repaint();
    }
//    public void rescale(){
//
//        SetDetails[] detailsArr = front.values().toArray(new SetDetails[front.size()]);
//        for(SetDetails details : detailsArr){
//
//        }
//
//    }
//    public void setCurrentFront(HashMap<Point, ChangeIdentifierSet> front){
//        double maxX = getMaxX();
//        double maxY = getMaxY();
//        for(Point point : front.keySet()){
//            if(point.getX()>maxX) maxX = point.getX();
//            if(point.getY()>maxY) maxY = point.getY();
//        }
//        setXRange(0, (int)Math.ceil(maxX));
//        setYRange(0, (int)Math.ceil(maxY));
//        this.front = front;
//    }
//    public void setCurrentMutation(Point mutation){
//        this.mutation = mutation;
//    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setColor(Color.red);
        if (!frontLocked) {
            try {
//                points = front.keySet().toArray(new Point[front.size()]);
                frontClone = (HashMap<Point, SetDetails>)front.clone();
                points = frontClone.keySet().toArray(new Point[frontClone.size()]);
            } catch (ConcurrentModificationException ex) {
            }
        }
        Color colour;
        SetDetails set;
        Point point;
        for (Point actPoint : points) {
            point = getScreenPosition(actPoint);
            if (actPoint != null) {
                set = frontClone.get(actPoint);
                colour = set.getColour();
                g2.setColor((colour != null ? colour : Color.GRAY));
                if (point.equals(selectedPoint)) {
                    g2.setColor(Color.blue);
                }
                g2.fillOval(point.x - (pointDiameter / 2),
                        point.y - (pointDiameter / 2), pointDiameter, pointDiameter);
                if (point.equals(selectedPoint)) {
                    g2.setColor(Color.red);
                }
            }
        }
    }

    @Override
    public void mouseClicked(MouseEvent arg0) {
        super.mouseClicked(arg0);
        Point point = new Point(arg0.getX(), arg0.getY());
        SetDetails setDetails = getSetDetailsWithinPointArea(point);
        if (setDetails != null) {
            getFrame().displaySetDetails(setDetails.getTimeTaken(),
                    setDetails.getError(), setDetails.getSet());
        }
        this.repaint();
    }

    private SetDetails getSetDetailsWithinPointArea(Point point) {
//        Point[] checkPoints = front.keySet().toArray(new Point[front.size()]);
        Point realLocation = getActualPosition(point);
        Point closestPoint = null;
        double closestDistance = (pointDiameter / 2);
        double distance;
        this.frontLocked = true;
        for (Point p : points) {
            distance = Math.sqrt(Math.pow((p.getX() - realLocation.getX()), 2) + Math.pow((p.getY() - realLocation.getY()), 2));
            if (distance <= closestDistance) {
                closestDistance = distance;
                closestPoint = p;
            }
        }
        this.frontLocked = false;
        if (closestPoint != null) {
            selectedPoint = closestPoint;
            return front.get(closestPoint);
        } else {
            return null;
        }
    }

}
